const basedata = require('./basedata/')
const base64 = require('./base64')
const common = require('./common')
const fetchUrl = require('./url')
// 云服务空间中独特的处理方法
const handle = require('./async/')
const {getResult, getError} = require('./handle')
// 与前端一致的处理方法
const {getCloud} = require('./sync/')
const {before, after, token} = require('./system/')
/**
 * 获取配置内容。适用于uni-config-center内的建立的文件夹并在内再建立配置文件
 * key：配置的键。该配置文件实际是一个json对象，该key用于获取该json对象中的某个值
 * name：配置的名称。文件夹内的文件名
 * pluginId：插件id。也是文件夹的名称。uni-config-center内的第一级文件夹
 */
const getConfigInfo = (pluginId, name, key) => {
	const uniConfigCenter = require('uni-config-center')
	if (uniConfigCenter) {
		var myConfig = uniConfigCenter({pluginId})
		if (myConfig && myConfig[name]) {
			myConfig = myConfig[name]()
			if (key) {
				return myConfig[key]
			}
			return myConfig
		}
	}
	return undefined
}

// 获取本云服务空间的数据库信息
const dbGet = (name) => {
	if (name.substr(-5) === '-test') {
		name = name.slice(0, -5)
	}
	return new Promise((resolve, reject) => {
		const db = uniCloud.database()
		db.collection('a-a-table').where({name}).get().then(res => {
			if (res.data.length > 0) {
				const data = res.data[0]
				basedata.SystemKey.forEach(key => {
					if (key !== '_id') { // 保留_id以供修改
						delete data[key]
					}
				})
				resolve(getResult({data}))
			} else {
				reject(getError(`没有数据库：“${name}”`))
			}
		}).catch(reject)
	})
}

/**
 * 综合处理
 * 是本云服务空间的数据库或者云函数，直接处理
 * 非本云服务空间的数据库或者云函数，通过对应的云服务空间的api处理
 * cloudData：云服务的基础参数
 * params：请求参数。详情参见handleWhere
 */
const handleCloud = (cloudData, params, config) => {
	var {
		space,
		name,
		detail,
		type = 'function', // function表示云函数云对象
		collection // 事务操作的数据库，依赖于doc的写法，也就说还必须只有单个的_id才生效。一般后端非jql语法操作事务锁时传入代替了db.collection(name)
	} = cloudData
	return new Promise((resolve, reject) => {
		if (common.nouserdb || common.necessary.User || type === 'get') {
			if (name.substr(-5) === '-test') {
				name = name.slice(0, -5)
			}
			const {text, cloud, url} = getCloud({space, name, detail, type}, false)
			if (cloud) { // 本服务空间内执行。也可能是相同类型的云服务空间，可以直接操作
				if (type === 'function') {
					/**
					 * 云函数云对象操作
					 * 云函数中直接进入即可，进入后是对数据库操作，走下面
					 * 所以并没有该操作方式的使用场景！
					 */
					reject(getError(`违规的云函数“${name}”操作`))
				} else {
					if (handle[type]) {
						const cloudObj = {cloud, name, dbGet, collection}
						if (type === 'remove' && params && params.ChildKey) {
							cloudObj.get = handle.get
						}
						handle[type](cloudObj, params, config).then(resolve).catch(reject)
					} else {
						reject(getError(`操作类型“${type}”不存在`))
					}
				}
			} else if (url) { // 其他类型的云服务空间，需通过url访问
				const urlParams = {space, name, where: params}
				fetchUrl(url, {data: {params: urlParams}}).then(resolve).catch(reject)
			} else {
				reject(getError('请求初始化错误'))
			}
		} else {
			reject(getError('请登录！'))
		}
	})
}

/**
 * 生成一个备案的编码
 */
const getCode = (data) => {
	const action = require('./getCode')
	return action(data, {get, add})
}

/**
 * 复制一个数据
 * @param {Object} params 查询条件 同get
 * 	space：云服务空间名称。一般就是本服务空间
 * 	name：数据库名称
 * 	_id或者doc：要复制的数据id
 * 	code：原始数据的code值，表示复制该数据需要生成新的code(根据编码规则获取编码前缀和项目前缀)，否则不需要
 * 
 * 1，名称和唯一项，前面都添加一个副本。这一步在addData中控制
 * 2，原code存在，重新获取code
 * 3，默认去除的信息：
 * 	"_id", "remark", "creator", "timestamp", "alert_user", "alert_time", "remove"
 * 4，默认的信息
 * creat_type：4
 * HISTORY：[{_id: '原id'}]
 */
const copy = (params) => {
	const {space, name, doc, _id, code, addData} = params
	if (_id) {
		params.where = {_id}
	}
	const {startTime, necessary: {User}} = common
	return new Promise((resolve, reject) => {
		// 1，查询该数据
		handleCloud({space, name, type: 'get'}, params).then(async res => {
			if (res.data.length === 1) {
				const data = res.data[0]
				// 2，是否需要code
				if (code) {
					var BM = '', XM = ''
					if (typeof(code) === 'string') {
						const arr = code.split('-')
						if (arr.length === 2) {
							// 这是目前code的生成规则
							BM = arr[0].substr(-2)
							XM = arr[1].substr(0, 2)
							const result = await getCode({BM, XM, space, name, type: 1})
							data.code = result.data
						}
					}
				}
				// 3，删除固定内容
				["_id", "remark", "creator", "timestamp", "alert_user", "alert_time", "remove"].map(key => {
					delete data[key]
				})
				// 4，复制新的内容
				addData && Object.keys(addData).forEach(key => {
					data[key] = addData[key]
				})
				// 5，默认的信息
				data.creat_type = 4
				data.creator = User
				data.timetamp = startTime
				data.HISTORY = [{_id: doc || _id, alert_user: User, alert_time: startTime}]
				handleCloud({space, name, type: 'add'}, {data, isReturn: true}).then(res => {
					res.message = '数据复制成功'
					resolve(getResult(res))
				}).catch(reject)
			} else {
				reject(getError('该数据已不存在'))
			}
		}).catch(reject)
	})
}

/**
 * 增加一个或多个数据
 * @param {Object} params
 *  space：云服务空间名称。只有特定的已知场景，需要对其他类型的云服务空间操作，才需要传
 * 	name：数据库名称
 * 	data：要增加的数据。对象或者对象数组
 * 	isReturn：是否真返回新增后的完整数据。返回的数据都是数组
 * 	addType：不满足必要性和唯一性条件的判断和处理
 * 		requiredType = -1，忽略必要性判断；0：不新增；1：仍然新增（就会导致必要的数据不完整）
 * 		onlyType = -1 -1，忽略唯一性判断；0：不新增；1：修改原数据（只修改本次已有的）；2：覆盖原数据（会保留_id）；3：覆盖原数据（不保留_id）
 * 		onlyKeys, 自定义的唯一性字段集合。同数据库配置设置，二维数组
 * 		required, 自定义的必要性字段集合。同数据库配置设置，一维数组
 */
const add = (params, config) => {
	const {space, name, data, isReturn, addType} = params
	return handleCloud({space, name, type: 'add'}, params, config)
}

/**
 * 删除一个或多个数据
 * @param {Object} params
 *  space：云服务空间名称。只有特定的已知场景，需要对其他类型的云服务空间操作，才需要传
 * 	name：数据库名称
 * 	doc：要删除的字符串，可以是数组。优先级最大
 * 	where：其他要删除的条件。可以是‘all’，表示删除全部，其它字符串表示jql语法。如果是对象，表示删除的查询条件
 * 	isDelete：是否真的删除数据
 * 
 * 删除时务必注意select类型查询时，必须保证每一个都有值，否则会删除所有数据！
 */
const remove = (params, config) => {
	const {space, name, doc, where, isDelete} = params
	return handleCloud({space, name, type: 'remove'}, params, config)
}

/**
 * 修改一个或多个数据
 * @param {Object} params
 *  space：云服务空间名称。只有特定的已知场景，需要对其他类型的云服务空间操作，才需要传
 * 	name：数据库名称
 * 	doc：要修改的字符串，可以是数组。优先级最大
 * 	where：其他要修改信息的的条件。字符串表示jql语法。如果是对象，表示修改的查询条件
 * 	data：要修改的数据
 * 	isReturn：是否真返回修改后的完整数据。返回的数据都是数组
 */
const alert = (params, config) => {
	const {space, name, doc, where, data, isReturn} = params
	return handleCloud({space, name, type: 'alert'}, params, config)
}

/**
 * 查询数据 - 前端的查询一般在前端完成。后端调用的查询走这里
 * @param {Object} params
 *  space：云服务空间名称。只有特定的已知场景，需要对其他类型的云服务空间操作，才需要传
 * 	name：数据库名称
 * 	doc：要查询的_id，可以是数组。优先级最大
 * 	where：其他要查询的条件。如果是字符串表示jql语法。如果是对象，表示删除的查询条件
 * 	pagination：页码控制器。pageIndex和pageSize需有效
 */
const get = (params, config) => {
	const {space, name, doc, where, pagination} = params
	var type = 'get'
	if (where) {
		if (where.getTreePath) {
			type = 'getTreeParent'
		} else if (where.getTree) {
			type = 'getTreeChild'
		}
	}
	return handleCloud({space, name, type: type}, params, config)
}

/**
 * 新增修改 - 查询到就修改，没有则新增
 * 先走查询，
 * 有数据则修改
 * 没有数据则新增
 * @param {Object} params
 *  space：云服务空间名称。只有特定的已知场景，需要对其他类型的云服务空间操作，才需要传
 * 	name：数据库名称
 * 	where：需修改的信息的条件，同查询。同时默认不返回新增或修改后的数据
 *  data：需新增或修改的信息。修改的时候有_id的话，会被自动去除
 * 	isReturn：是否真返回修改后的完整数据。返回的数据都是数组
 */
const addAlert = (params, config) => {
	// 一般如果新增会默认返回信息，修改不返回。内部会处理
	const {space, name, doc, where, data, isReturn} = params
	return handleCloud({space, name, type: 'addAlert'}, params, config)
}

/**
 * 删除新增 - 查询到满足条件的数据后完全删除，再新增。
 * 新增时不走校验过程，直接新增，适用于上传数据的覆盖性操作
 * @param {Object} params
 *  space：云服务空间名称。只有特定的已知场景，需要对其他类型的云服务空间操作，才需要传
 * 	name：数据库名称
 * 	doc：要删除的字符串，可以是数组。优先级最大
 * 	where：删除前需要修改的信息的条件，同查询。同时默认不返回新增后的数据
 *  data：需新增的信息。内部有判断添加where中的非对象数据，若要保证准确性，请在调用前处理
 * 	isReturn：是否真返回修改后的完整数据。返回的数据都是数组
 */
const removeAdd = (params, config) => {
	// 一般新增会默认不返回信息
	const {space, name, doc, where, data, isReturn, isDelete, MainKey} = params
	return handleCloud({space, name, type: 'removeAdd'}, params, config)
}

/**
 * 批量修改
 * 数据库的逻辑是只能修改单个。批量修改的逻辑是，先查询再删除后新增。
 * 查询到数据后，写入HISTORY数据，再根据_id全部真的删除，然后全部新增
 * @param {Object} params
 * 	space：云服务空间名称。只有特定的已知场景，需要对其他类型的云服务空间操作，才需要传
 * 	name：数据库名称
 * 	data：对象。key为_id，value为要修改的内容
 * 	isReturn：是否真返回修改后的完整数据。返回的数据都是数组
 */
const batchAlert = (params, config) => {
	// 一般新增会默认不返回信息
	const {space, name, data, isReturn} = params
	return handleCloud({space, name, type: 'batchAlert'}, params, config)
}

/**
 * 云对象内部的预处理
 * @param {Object} request：当前请求
 * @param {Object || Boolean} demand：当前项目要求。对象或者true，对象是具体要求；true是合法性和有效性验证
 */
const handleBefore = (request, demand, isFirst) => {
	before(request, demand, common, basedata, base64, isFirst)
}
// 云对象内部的后处理
const handleAfter = (request, error, result, isLast) => {
	return after(request, error, result, common, isLast)
}

// 获取用户信息。params若不存在，则默认以necessary.User为id进行查询
const getUserInfo = (params, isList) => {
	var {User, Target} = common.necessary
	if (!params && User) {
		params = {_id: User}
	}
	if (typeof(params) === 'string') {
		// 表示是项目代码
		Target = params
		// 默认查询当前用户
		params = {_id: User}
	} else {
		var {space, name = 'a-user', target} = params
		Target = target
		delete params.target
		delete params.space
		delete params.name
		if (Object.keys(params).length === 0) {
			params._id = User
		}
	}
	// 通过项目代码获取云服务空间名称以及用户数据库
	const arr = Object.keys(basedata)
	for (let i = 0; i < arr.length; i++) {
		const obj = basedata[arr[i]]
		if (Object.prototype.toString.call(obj) === '[object Object]') {
			if (obj.target.indexOf(Target) > -1) {
				space = arr[i]
				name = obj.userDb || 'a-user'
				break
			}
		}
	}
	return new Promise((resolve, reject) => {
		if (params) {
			handleCloud({space, name, type: 'get'}, params).then(res => {
				if (isList) {
					resolve(res.data)
				} else {
					if (res.data.length > 0) {
						resolve(res.data[0])
					} else {
						reject(getError('没有用户信息'))
					}
				}
			}).catch(reject)
		} else {
			reject(getError('缺少用户参数'))
		}
	})
}

/**
 * redis操作
 * 获取、写入、更新
 */
const redisaction = ({
	key,
	type = 'get', // get：获取；set：写入；update：更新,
	data, // 需要写入的数据
	expMinute = 100, // 有效时长，单位秒
	params, // 获取时，如果没有，则查询，查询后并写入。需与keys内容共同决定
	keys = [], // 只需要的字段。redis的存储空间有限，只存储必要的内容。必须要有值，没有值，默认不查询
	isList, // 查询结果是否数组，
	addData, // 查询不到时的新增参数。新增完毕后，同时写入。也需要keys支持
}) => {
	return new Promise((resolve, reject) => {
		if (key) {
			try {
				const redis = uniCloud.redis()
				switch (type){
					case 'get':
					{
						/**
						 * 如果redis中没有，则先查询，需keys支持，并重新写入redis
						 * redis本身也有有效时间，过了有效时间后就会自动删除，则也会先查询再重新写入
						 */
						const fetchAction = () => {
							if (params && keys && keys.length > 0) {
								// 处理查询和新增
								const handleResData = (res, callback) => {
									var result = []
									res.data.map(item => {
										var obj = {}
										keys.map(str => {
											obj[str] = item[str]
										})
										result.push(obj)
									})
									result = isList ? result : result.length > 0 ? result[0] : undefined
									if (result) {
										redis.set(key, result, expMinute, 'EX').then(res => {
											resolve(result)
										}).catch(err => {
											reject(err)
										})
									} else {
										callback && callback()
									}
								}
								
								handleCloud({name, type: 'get'}, params).then(res => {
									handleResData(res, () => {
										// 该结果可能需要额外处理
										if (addData) {
											handleCloud({name, type: 'add'}, addData).then(res => {
												handleResData(res)
											}).catch(reject)
										} else {
											reject('没有查询结果')
										}
									})
								}).catch(reject)
							} else {
								reject('没有查询参数')
							}
						}
						redis.get(key).then(res => {
							if (res) {
								resolve(res)
							} else {
								fetchAction()
							}
						}).catch(err => {
							fetchAction()
						})
					}
						break
					case 'set':
						redis.set(key, data, expMinute, 'EX').then(res => {
							resolve(res)
						}).catch(err => {
							reject(err)
						})
						break
					case 'update':
						reject(err)
						break
					default:
						break
				}
			} catch(e) {
				reject(e)
			}
		}
	})
}

/**
 * 事务的可回滚操作。只能在云函数中正常操作，不支持jql语法
 * 	对于修改和删除仅支持使用doc方法，不支持使用where方法，updateAndReturn除外。
 *	新增时使用add方法一次只可以新增单条，不可新增多条，即不支持在add方法内传入数组
 *	腾讯云没有限制where的使用，但是使用where修改或删除多条会导致无法回滚
 * callback：需要等待的操作，必须是返回Promise的方法，执行参数是前面执行的结果；或者是Promise对象，则没有执行参数
 * 
 * https://doc.dcloud.net.cn/uniCloud/cf-database.html#transaction
 */
const transaction = ({
	name, // 数据库名称。将自动添加‘-test’
	doc, // 数据id
	type = 'update', // 操作类型。仅限add、update和remove
	params, // 操作参数。新增时只能是单个对象；修改时，也是单个对象；删除为空
	isReturn = true,
	isDelete = false,
	addType
} = {}, callback) => {
	return new Promise((resolve, reject) => {
		name = handle.getDbName(name)
		if (name) {
			uniCloud.database().startTransaction().then(transaction => {
				/**
				 * 这是使用了既定的增删改的逻辑。params仅是增改的参数。好处是返回值与固有逻辑一致
				 * 固有逻辑特点是删和改必须用doc
				 * 如果不使用固有逻辑，则要使用
				 * db = transaction.collection(name)
				 * db.add(params)
				 * db.doc(doc)[type](params)
				 * update不改为alert
				 */
				if (type === 'update') {
					type = 'alert'
				}
				handleCloud({name, type, collection: transaction.collection(name)}, {data: params, doc, isReturn, isDelete, addType}).then(res => {
					const result = {
						message: `${name}事务处理完成`,
						data: res.data || res
					}
					const fail = (err) => {
						if (err === 0) { // callback处理成功，但是执行失败
							console.log(`${name}事务执行失败`)
							reject(getError('事务出错'))
						} else if (err === 1) { // callback处理失败，但是回滚成功
							console.log(`${name}事务回滚成功`)
							reject(getError('事务出错'))
						} else { // 具体的reject错误，正常是不应该走这一步的，需要特殊对待
							console.error(err)
							reject(getError(`事务出错，请联系管理员`))
						}
					}
					const success = (res) => {
						if (!res || res.success) { // 执行成功了，res为何为空？
							// reject(getError('事务出错')) // 测试用。以免直接成功无法二次复用
							resolve(getResult(result))
						} else { // callback处理成功，但是执行失败
							fail(0)
						}
					}
					if (callback) {
						var bool = typeof(callback) === 'function'
						var action = bool ? callback(res) : callback
						action.then(res => {
							/**
							 * 此处遗留问题，action执行成功后这一步却执行失败，
							 * 暂时的对策是重复执行一次
							 * 以后若有好的办法再跟进
							 */
							transaction.commit().then(success).catch(err => {
								transaction.commit().then(success).catch(fail)
							})
						}).catch(err => {
							transaction.rollback().then(res => {
								// callback处理失败，但是回滚成功
								fail(1)
							}).catch(fail)
						})
					} else {
						transaction.commit().then(success).catch(fail)
					}
				}).catch(reject)
			})
		}
	})
}

module.exports = {
	basedata, dbGet,
	base64, common, token,
	getError, getResult,
	getDbName: handle.getDbName,
	getNecessary: handle.getNecessary,
	getConfigInfo, getCode,
	
	// 综合处理。可直接调用
	handleCloud,
	// 访问外链URL
	fetchUrl,
	
	copy, // 复制一个数据
	add, // 新增数据
	alert, // 修改数据
	remove, // 删除数据
	get, // 查询数据
	batchAlert, // 批量修改数据
	removeAdd, // 删除并新增数据
	addAlert, // 新增或修改数据
	
	handleBefore, // 预处理
	handleAfter, // 后处理
	
	redisaction, // redis操作
	getUserInfo, // 获取用户信息
	transaction // 可回滚事务处理
}
